﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using MazeModel;
using System.Drawing.Text;

namespace MazeDrawer
{


    public partial class MazeForm : Form
    {

        MazeController mc;
        int keyInput = 0;                       // What input must go into MazeModel next
        bool[] pressedKeys = new bool[5];       // Which arrow buttons are being held down right now
        int displayedScore = 0;

        bool editMode = false;                  // Ehen true, edit mode is enabled
        bool editClicking = false;              // Ehen true, user is holding mousebutton down in edit mode
        int editBrush = 0;                      // Ehich value is selected in edit move (1: wall, 2: start, etc)
        int[] editStart = new int[2];           // Ehere is the start located when in edit mode

        private Image treasureImage;            // Image of collectable treasure
        private Image wallImage;                // Image of impenentrable wall
        private Image playerImage;              // Image of player avatar
        private Image exitImage;                // Image of the exit
        private Image wallhuggerImage;

        PrivateFontCollection pfc;
        private Font standardFont;

        List<Decal> listOfDecals = new List<Decal>(); // List of decals (floating score feedback)
 
        public MazeForm()
        {

            // Enable Double Buffer to not have flicker
            this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
            InitializeComponent();
        }

        private void MazeForm_Load(object sender, EventArgs e)
        {

            // Load our custom font
            pfc = new PrivateFontCollection();
            // Font downloaded from: http://www.fontspace.com/codeman38/press-start
            pfc.AddFontFile("data/codeman38_press-start/prstart.ttf");

            FontFamily fontFam = new FontFamily("Press Start", pfc);
            Font fontBanner = new Font(fontFam, 35, FontStyle.Regular);
            standardFont = new Font(fontFam, 11);

            // Set size and background color of window
            this.ClientSize = new Size(640, 480);
            this.BackColor = System.Drawing.Color.Black;

            // Create and initialize our MazeController
            mc = new MazeController();
            mc.collect += this.collect;
            mc.completeLevel += this.completeLevel;
            mc.die += this.die;
            mc.gameOver += this.gameOver;

            // Make the MazeController load this level
            mc.loadLevel("data/welcome.txt");

            // Load the images from resources
            playerImage = new Bitmap(Properties.Resources.guyS);
            treasureImage = new Bitmap(Properties.Resources.treasure);
            wallImage = new Bitmap(Properties.Resources.wall);
            exitImage = new Bitmap(Properties.Resources.exit);
            wallhuggerImage = new Bitmap(Properties.Resources.wallhugger);
        }

        private void gameOver()
        {
            this.displayedScore = mc.lastScore;
            gameOverTimer.Start();
        }

        private void die()
        { 
            // Player has died. Now the death screen will be shown for a few seconds.
            listOfDecals.Clear();
            deathTimer.Start();
            mc.Restart();
        }

        private void collect(int amount, int x, int y) 
        {
            // This stuff in here will run whenever player collects treasure
            // the integer "i" contains how much score was collected
            // x and y are the real actual window coordinates of the location
            listOfDecals.Add(new Decal(Convert.ToString(amount), x-10, y+10));
        }

        private void completeLevel()
        { 
            // This runs when player finishes the level
            displayedScore = mc.mazePlayer.score;
            listOfDecals.Clear();
            mc.randomizeMaze();
            this.levelClearTimer.Start();
        }

        private void deathTimer_Tick(object sender, EventArgs e)
        {
            // Death screen is over
            deathTimer.Stop();
        }

        private void levelClearTimer_Tick(object sender, EventArgs e)
        {
            // Level clear screen is over
            levelClearTimer.Stop();
        }

        private void gameOverTimer_Tick(object sender, EventArgs e)
        {
            gameOverTimer.Stop();
        }
      
        // Main Game Tick Loop Thingy
        private void gameTimer_Tick(object sender, EventArgs e)
        {
            // Remove old decals
            for (int i = 0; i < listOfDecals.Count(); i++)
            {
                if (listOfDecals[i].GetTtl() < 0)
                {
                    listOfDecals.RemoveAt(i);
                }
            }
            if ((!deathTimer.Enabled) && (!levelClearTimer.Enabled) && (!gameOverTimer.Enabled) && (!editMode))
                mc.gameTick(keyInput);
            this.Invalidate();
            
        }

        private void MazeForm_Paint(object sender, PaintEventArgs e)
        {
            // Draw the level cleared screen
            if (levelClearTimer.Enabled)
            {
                e.Graphics.FillRectangle(Brushes.Black, 0, 0, 640, 480);
                e.Graphics.DrawString("LEVEL " + mc.level, standardFont, Brushes.White, 260, 200);
                e.Graphics.DrawString("SCORE: " + displayedScore, standardFont, Brushes.White, 250, 300);
                if (displayedScore < mc.mazePlayer.score)
                    displayedScore += 100;
                return;
            }
            // Draw the GAME OVER screen
            if (gameOverTimer.Enabled)
            {
                e.Graphics.FillRectangle(Brushes.Black, 0, 0, 640, 480);
                e.Graphics.DrawString("GAME OVER", standardFont, Brushes.White, 260, 200);
                e.Graphics.DrawString("SCORE: " + displayedScore, standardFont, Brushes.White, 250, 300);
                return;
            }
            // Draw the death screen
            if (deathTimer.Enabled)
            {
                e.Graphics.FillRectangle(Brushes.Black, 0, 0, 640, 480);
                e.Graphics.DrawString("YOU ARE DEAD\n" + mc.remainingLives + " LIVES LEFT", standardFont, Brushes.Red, 240, 200);
                e.Graphics.DrawString("Remaining lives: " + mc.remainingLives, standardFont, Brushes.White, 0, 0);
                return;
            }
                
            // If neither the death screen or the level cleared screen is on, the game screen
            // will be drawn instead. Duh. 

            // BeginX and beginY are offsets we can use to make the maze start being drawn wherever we want.
            // Useful because we need some space at the top for info bar.
            int beginX = 0;
            int beginY = 15;

            // Draw the top info, if in edit mode then draw brushes, otherwise draw info
            if (editMode)
            {
                e.Graphics.DrawImage(wallImage, 16, 0);
                e.Graphics.DrawImage(playerImage, 16 * 2, 0);
                e.Graphics.DrawImage(exitImage, 16 * 3, 0);
                //e.Graphics.FillRectangle(Brushes.Green, 16*3,0,16,16);
                e.Graphics.DrawImage(treasureImage, 16 * 4, 0);
                e.Graphics.DrawImage(wallhuggerImage, 16 * 5, 0);
                e.Graphics.DrawRectangle(new Pen(Brushes.Red), editBrush * 16, 0, 15, 15);
                e.Graphics.DrawString("Edit Mode. C: Clear, S: Save test.txt", standardFont, Brushes.White, 95, 1);
            }
            else
            {
                e.Graphics.DrawString("LEVEL: " + mc.level, standardFont, Brushes.White, 0, 1);
                e.Graphics.DrawString("SCORE: " + mc.mazePlayer.score, standardFont, Brushes.White, 170, 1);
                e.Graphics.DrawString("BONUS: " + mc.remainingBonus, standardFont, Brushes.White, 400, 1);
            }

            // Draw walls
            for (int i = 0; i != mc.mazeMaze.getHeight(); i++)
            {
                // The line i is being drawn
                for (int j = 0; j != mc.mazeMaze.getWidth(); j++)
                {
                    // in line i, j squares have currently been drawn
                    if (mc.mazeMaze.getCellAt(i, j) == 1)
                        e.Graphics.DrawImage(wallImage,
                                j * 16 + 1 + beginX,
                                i * 16 + 1 + beginY);
                }
            }

            // Draw player
            e.Graphics.DrawImage(playerImage,
                    mc.mazePlayer.getPos()[1] * 16 + 1 + (2*mc.mazePlayer.getSubPos()[1]) + beginX,
                    mc.mazePlayer.getPos()[0] * 16 + 1 + (2*mc.mazePlayer.getSubPos()[0]) + beginY);

            // Draw end(s)
            foreach (MazeObject end in mc.mazeEnds)
            {
                e.Graphics.DrawImage(exitImage,
                        end.getPos()[1] * 16 + 1 + beginX,
                        end.getPos()[0] * 16 + 1 + beginY);
            }
            // Draw treasure(s)
            foreach (MazeTreasure treasure in mc.mazeTreasures) 
            {
                e.Graphics.DrawImage(treasureImage,
                        treasure.getPos()[1] * 16 + 1 + beginX,
                        treasure.getPos()[0] * 16 + 1 + beginY);
            }
            // Draw EnemyWallhuggers
            foreach (EnemyWallhugger enm in mc.enemyWHlist)
            {
                e.Graphics.DrawImage(wallhuggerImage,
                        enm.getPos()[1] * 16 + 1 + (2 * enm.getSubPos()[1]) + beginX,
                        enm.getPos()[0] * 16 + 1 + (2 * enm.getSubPos()[0]) + beginY);
            }
            // Draw decals!
            foreach (Decal decal in listOfDecals)
            {
                e.Graphics.DrawString(decal.GetLabel(), standardFont, Brushes.Silver, decal.GetPosX(), decal.GetPosY());
            }
        }

        private void MazeForm_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            // Player presses button
            switch (e.KeyValue)
            {
                case 38:            // up
                    keyInput = 1;
                    break;
                case 40:            // down
                    keyInput = 2;
                    break;
                case 37:            // left
                    keyInput = 3;
                    break;
                case 39:            // right
                    keyInput = 4;
                    break;
            }
            pressedKeys[keyInput] = true;

            // (hopefully) temporary shit-mix - pressing the S button saves the current level
            if (e.KeyCode == Keys.S)
                mc.mazeMaze.SaveLevelToTextFile("data/test.txt");

            // (hopefully) temporary shit-mix - pressing the e button enables/disables edit mode
            if (e.KeyCode == Keys.E)
            {
                if (this.editMode == false)
                {
                    this.editMode = true;
                    this.gameTimer.Stop();
                    mc.mazeMaze.SetSize(29, 40);
                    this.Invalidate();
                }
                else
                {
                    this.editMode = false;
                    this.gameTimer.Start();
                    this.Invalidate();
                }
            }
            if ((e.KeyCode == Keys.C) && editMode)
                this.ClearMaze();
            if (e.KeyCode == Keys.R)
            {
                mc.mazeMaze.randomize(2);
                mc.Restart();
            }
        }

        private void MazeForm_KeyUp(object sender, KeyEventArgs e)
        {
            // Player releases button
            switch (e.KeyValue)
            {
                case 38:                        // up
                    pressedKeys[1] = false;
                    break;
                case 40:                        // down
                    pressedKeys[2] = false;
                    break;
                case 37:                        // left
                    pressedKeys[3] = false;
                    break;
                case 39:                        // right
                    pressedKeys[4] = false;
                    break;
            }
            // Re-evaluate keyInput since some button was released
            keyInput = 0;
            for (int i = 4; i > 0; i--)
            {
                if (pressedKeys[i])
                {
                    keyInput = i;
                    continue;
                }
            }
        }

        private void MazeForm_MouseDown(object sender, MouseEventArgs e)
        {
            // Don't do anything if not in edit mode!
            if (!editMode)
                return;

            editClicking = true;
            int i = (this.PointToClient(Cursor.Position).Y / 16) - 1;
            int j = this.PointToClient(Cursor.Position).X / 16;

            // If i or j go outside the window, don't do anything
            if ((i < -1)
                || (j < 0)
                || (i > mc.mazeMaze.getHeight())
                || (j > mc.mazeMaze.getWidth()))
                return;

            // Not we have i and j pointing to the cell where the mouse pointer is clicking
            // However, if i = -1, then the mouse pointer must be in the top brush selector bar
            // (remember, the whole maze is moved downwards one block's worth for info bar)
            if (i == -1)
            {
                editBrush = j;
            }
            // If it's a right-click, sample and set to the value of this cell
            else if (e.Button == MouseButtons.Right)
            {
                editBrush = mc.mazeMaze.getCellAt(i, j);
            }
            // If this is running, it must be a left-click somewhere inside the maze
            else
            {
                // Special considerations for when placing Start - we don't want two or more start locations!
                if (editBrush == 2)
                {
                    mc.mazeMaze.setCell(editStart[0], editStart[1], 0);
                    editStart[0] = i; editStart[1] = j;
                }
                mc.mazeMaze.setCell(i, j, editBrush);
                mc.Restart();
            }
            this.Invalidate();
        }

        private void MazeForm_MouseUp(object sender, MouseEventArgs e)
        {
            editClicking = false;
        }

        private void MazeForm_MouseMove(object sender, MouseEventArgs e)
        {
            // Don't do anything if either not in edit mode, or mousebutton is not down!
            if ((!editMode) || (!editClicking))
                return;

            // If in edit mode and mouse button is down, do mouseclick here now
            MazeForm_MouseDown(sender, e);
        }

        // Clears the maze, leaving only the outside walls
        private void ClearMaze()
        {
            for (int i = 1; i != mc.mazeMaze.getHeight() - 1; i++)
                for (int j = 1; j != mc.mazeMaze.getWidth() - 1; j++)
                    mc.mazeMaze.setCell(i, j, 0);
            this.Invalidate();
            mc.Restart();
        }
    }
}
